package store.lunangangster.engine.impl.helper;

import java.util.Map;
import store.lunangangster.bpmn.model.FlowElement;
import store.lunangangster.bpmn.model.Process;
import store.lunangangster.engine.NodeFlowException;
import store.lunangangster.engine.impl.Context;
import store.lunangangster.engine.impl.entity.ExecutionEntity;
import store.lunangangster.engine.impl.entity.ProcessDefinitionEntity;
import store.lunangangster.engine.impl.entity.mgr.ExecutionEntityManager;
import store.lunangangster.engine.impl.interceptor.CommandContext;
import store.lunangangster.engine.impl.runtime.ProcessInstance;
import store.lunangangster.engine.repository.ProcessDefinition;

public class ProcessInstanceHelper {

  public ProcessInstance createProcessInstance(ProcessDefinition processDefinition, String processInstanceName) {
    return createProcessInstanceAndStart(processDefinition, processInstanceName, false, null);
  }

  public ProcessInstance createProcessInstanceAndStart(ProcessDefinition processDefinition, String processInstanceName) {
    return createProcessInstanceAndStart(processDefinition, processInstanceName, true, null);
  }

  public ProcessInstance createProcessInstanceAndStart(ProcessDefinition processDefinition, String processInstanceName, Map<String, Object> variables) {
    return createProcessInstanceAndStart(processDefinition, processInstanceName, true, variables);
  }

  /**
   * 创建并且开启一个流程实例，并且在创建的同时，添加一组流程变量
   * @param processDefinition     流程定义
   * @param processInstanceName   流程实例名
   * @param startInstance         是否开启实例
   * @param variables             流程变量
   * @return                      流程实例
   */
  protected ProcessInstance createProcessInstanceAndStart(ProcessDefinition processDefinition, String processInstanceName, boolean startInstance, Map<String, Object> variables) {
    // find BpmnModel for ProcessDefinition
    Process process = ProcessDefinitionUtil.getProcess(processDefinition.getId());
    if (process == null) {
      throw new NodeFlowException("创建ProcessInstance失败，找不到ProcessDefinition + " + processDefinition.getName() + "(id = " + processDefinition.getId() + " )");
    }
    FlowElement initialElement = process.getInitialElement();
    if (initialElement == null) {
      throw new NodeFlowException("创建ProcessInstance失败，对于ProcessDefinition + " + processDefinition.getName() + "(id = " + processDefinition.getId() + " )， 找不到InitialFlowElement");
    }
    // create ProcessInstance
    ExecutionEntity processInstance = createProcessInstanceWithInitialFlowElement(
        processDefinition, processInstanceName, initialElement, variables);

    if (startInstance) {
      CommandContext commandContext = Context.getCommandContext();
      startProcess(commandContext, processInstance);
    }
    return processInstance;
  }

  /**
   * 通过Process模型中所属的初始FlowElement元素，来创建ProcessInstance。该方法一般会创建两个Execution：
   * <ol>
   *   <li>针对此次流程的执行实例：ProcessInstance</li>
   *   <li>针对该流程中的第一个任务：InitialElement的Execution表示</li>
   * </ol>
   * @param processDefinition     流程定义
   * @param processInstanceName   流程实例名
   * @param initialElement        流程开始元素
   * @param variables             流程变量
   * @return                      流程实例
   */
  protected ExecutionEntity createProcessInstanceWithInitialFlowElement(
      ProcessDefinition processDefinition, String processInstanceName, FlowElement initialElement, Map<String, Object> variables) {
    ExecutionEntityManager executionManager = Context.getProcessEngineConfiguration().getExecutionEntityManager();

    // create root ProcessInstance
    ExecutionEntity processInstanceExecution
        = executionManager.createProcessInstanceExecution(processDefinition, processInstanceName);
    // set Variables
    processInstanceExecution.setVariables(variables);

    // create child Execution for initialFlowElement
    ExecutionEntity childExecution = executionManager.createChildExecution(processInstanceExecution);
    // set current FlowElement
    childExecution.setCurrentFlowElement(initialElement);

    return processInstanceExecution;
  }

  protected void startProcess(CommandContext commandContext, ExecutionEntity processInstance) {
    processInstance.setStartTime(commandContext.getProcessEngineConfiguration().getEngineClock()
        .now());
    for (ExecutionEntity childExecution : processInstance.getChildExecutions()) {
      childExecution.setStartTime(processInstance.getStartTime());
    }
    // simply let's first child go next
    commandContext.getAgenda().addDoElementBehaviorOperation(processInstance.getChildExecutions().get(0), null);
  }

  protected void fillProcessInstanceValueForExecution(final ExecutionEntity execution) {
    if (execution != null) {

      ProcessDefinitionEntity processDefinitionEntity = Context.getProcessEngineConfiguration()
          .getProcessDefinitionEntityManager().selectById(execution.getProcessDefinitionId());
      execution.setProcessDefinitionId(processDefinitionEntity.getKey());
      execution.setProcessDefinitionKey(processDefinitionEntity.getId());

    }
  }
}
